// Copyright 2011, Vanya Davidenko.
// Используемая кодировка: UTF-8.

#ifndef LIBV_UNICODE_H_
#define LIBV_UNICODE_H_

#include <string>
#include <utility>
#include <libv/cast.h>
#include <libv/check.h>
#include <libv/stdint.h>



namespace libv {

typedef char Utf8Char;
typedef uint16_t Utf16Char;
typedef uint32_t Utf32Char;
typedef uint32_t Codepoint;
struct SurrogatePair {
    SurrogatePair(const Utf16Char& high_, const Utf16Char& low_)
        : high(high_),
          low(low_) {}
    Utf16Char high;
    Utf16Char low;
};

::std::string

typedef ::std::basic_string<Utf8Char,
                            ::std::char_traits<Utf8Char>,
                            ::std::allocator<Utf8Char> > Utf8String;
typedef ::std::basic_string<Utf16Char,
                            ::std::char_traits<Utf16Char>,
                            ::std::allocator<Utf16Char> > Utf16String;
typedef ::std::basic_string<Utf32Char,
                            ::std::char_traits<Utf32Char>,
                            ::std::allocator<Utf32Char> > Utf32String;

enum { MIN_CODEPOINT = 0, MAX_CODEPOINT = 0x10FFFF,
       MIN_SUPPLEMENTARY_CODE_POINT = 0x10000,
       MIN_HIGH_SURROGATE = 0xD800, MAX_HIGH_SURROGATE = 0xDBFF,
       MIN_LOW_SURROGATE = 0xDC00, MAX_LOW_SURROGATE = 0xDFFF,
       UTF8_1_BYTE_SEQ_FIRST_BYTE_MAX = 0x7F,
       UTF8_CONTINUATION_BYTE_MAX = 0xBF,
       UTF8_2_BYTES_SEQ_FIRST_BYTE_MAX = 0xDF,
       UTF8_3_BYTES_SEQ_FIRST_BYTE_MAX = 0xEF,
       UTF8_4_BYTES_SEQ_FIRST_BYTE_MAX = 0xF7,

       // http://unicode.org/faq/utf_bom.html
       HIGH_OFFSET = MIN_HIGH_SURROGATE - (MIN_SUPPLEMENTARY_CODE_POINT >> 10),
       SURROGATE_OFFSET = MIN_SUPPLEMENTARY_CODE_POINT -
                          (MIN_HIGH_SURROGATE << 10) - MIN_LOW_SURROGATE
};



inline bool IsValidCodepoint(const Codepoint& c) {
    return MIN_CODEPOINT <= c && c <= MAX_CODEPOINT;
}

inline bool IsHighSurrogate(const Utf16Char& c) {
    return MIN_HIGH_SURROGATE <= c && c <= MAX_HIGH_SURROGATE;
}

inline bool IsLowSurrogate(const Utf16Char& c) {
    return MIN_LOW_SURROGATE <= c && c <= MAX_LOW_SURROGATE;
}

inline bool IsHalfOfSurrogate(const Utf16Char& c) {
    return MIN_HIGH_SURROGATE <= c && c <= MAX_LOW_SURROGATE;
}

inline bool IsValidSurrogatePair(const SurrogatePair& sp) {
    return IsHighSurrogate(sp.high) && IsLowSurrogate(sp.low);
}

inline bool IsRepresentedBySurrogatePair(const Codepoint& u) {
    assert(IsValidCodepoint(u));
    return u >= MIN_SUPPLEMENTARY_CODE_POINT;
}



inline Codepoint SurrogatePairToCodepoint(const SurrogatePair& sp) {
    PRECOND(IsValidSurrogatePair(sp));
    return SurrogatePairToCodepointUnsafe(sp);
}

inline Codepoint SurrogatePairToCodepointUnsafe(const SurrogatePair& sp){
    return (static_cast<Codepoint>(sp.high) << 10) + sp.low + SURROGATE_OFFSET;
}


inline SurrogatePair CodepointToSurrogatePair(const Codepoint& cp) {
    PRECOND(IsValidCodepoint(cp));
    return CodepointToSurrogatePairUnsafe(cp);
}

inline SurrogatePair CodepointToSurrogatePairUnsafe(const Codepoint& cp){
    return SurrogatePair(static_cast<Utf16Char>(HIGH_OFFSET + (cp >> 10)),
                         static_cast<Utf16Char>(MIN_LOW_SURROGATE+(cp&0x3FF)));
}



// Определение длины utf8-последовательности (1 символа) по первому байту.
// @exception VerifyException если переданный байт не может быть первым.
inline size_t Utf8ByteSequenceLength(const Utf8Char c) {
    const uint8_t uc = c;  // явно беззнаковый
    if ( uc <= UTF8_1_BYTE_SEQ_FIRST_BYTE_MAX ) {
        return 1U;
    } else if ( uc <= UTF8_CONTINUATION_BYTE_MAX ) {
        VERIFY_MSG(false, "Non-unicode utf-8 sequence start");
    } else if ( uc <= UTF8_2_BYTES_SEQ_FIRST_BYTE_MAX ) {
        return 2U;
    } else if ( uc <= UTF8_3_BYTES_SEQ_FIRST_BYTE_MAX ) {
        return 3U;
    } else if ( uc <= UTF8_4_BYTES_SEQ_FIRST_BYTE_MAX ) {
        return 4U;
    }
    VERIFY_MSG(false, "Non-unicode utf-8 sequence start");
    return false;  // недостижимая точка
}


inline size_t Utf16CodeUnitsSequenceLength(const Utf16Char c) {
    PRECOND(!IsLowSurrogate(c));
    return Utf16CodeUnitsSequenceLengthUnsafe(c);
}

inline size_t Utf16CodeUnitsSequenceLengthUnsafe(const Utf16Char c) {
    return 1U + static_cast<size_t>(IsHighSurrogate(c));
}



// Декодирование utf8-последовательности (1 символ), начинающейся с позиции pos.
inline Codepoint Utf8ByteSequenceDecode(const Utf8String& utf8_s, size_t* pos) {
    PRECOND(pos != NULL);
    PRECOND(*pos < utf8_s.size());

    Codepoint result = 0;
    size_t bytes_left = 0;

    for ( ; *pos < utf8_s.size() ; ++(*pos) ) {
        const uint8_t c = utf8_s[*pos];  // явно беззнаковое

        if ( c <= UTF8_1_BYTE_SEQ_FIRST_BYTE_MAX ) {
            VERIFY(bytes_left == 0);
            ++(*pos);
            return c;

        } else if ( c <= UTF8_CONTINUATION_BYTE_MAX ) {
            VERIFY(bytes_left > 0);
            result = ( ( result << 6 ) | ( c & 0x3F ) );
            if ( --bytes_left == 0 ) {
                VERIFY(IsValidCodepoint(result));
                ++(*pos);
                return result;
            }

        } else if ( c <= UTF8_2_BYTES_SEQ_FIRST_BYTE_MAX ) {
            VERIFY(bytes_left == 0);
            bytes_left = 1;
            result = ( c & 0x1F );

        } else if ( c <= UTF8_3_BYTES_SEQ_FIRST_BYTE_MAX) {
            VERIFY(bytes_left == 0);
            bytes_left = 2;
            result = ( c & 0x0F );

        } else if ( c <= UTF8_4_BYTES_SEQ_FIRST_BYTE_MAX ) {
            VERIFY(bytes_left == 0);
            bytes_left = 3;
            result = ( c & 0x07 );

        } else {
            VERIFY_MSG(false, "Non-unicode utf-8 sequence start");
        }
    }
    VERIFY_MSG(false, "Unexpected end of utf-8 sequence");
    return false;  // недостижимая точка
}

inline Codepoint Utf16CodeUnitSequenceDecode(const Utf16String& utf16_s,
                                             size_t* pos) {
    PRECOND(pos != NULL);
    PRECOND(*pos < utf16_s.size());

    const uint16_t c = utf16_s[*pos];
    if ( IsHighSurrogate(c) ) {
        VERIFY(*pos + 1 < utf16_s.size());
        (*pos) += 2;
        const SurrogatePair sp(utf16_s[*pos - 2], utf16_s[*pos - 1]);
        return SurrogatePairToCodepoint(sp);
    }

    VERIFY(!IsLowSurrogate(c));
    (*pos) += 1;
    return utf16_s[*pos - 1];
}



namespace internal {

inline void PushBackCodepoint(const Codepoint w, Utf16String* str) {
    assert(IsValidCodepoint(w));
    assert(str != NULL);

    if ( w >= MIN_SUPPLEMENTARY_CODE_POINT ) {
        const SurrogatePair sp = CodepointToSurrogatePair(w);
        str->push_back(sp.high);
        str->push_back(sp.low);
    } else {
        VERIFY(!IsHalfOfSurrogate(w));
        str->push_back(libv::IntegerCast<Utf16Char>(w));
    }
}


inline void PushBackCodepoint(const Codepoint w, Utf32String* str){
    assert(IsValidCodepoint(w));
    assert(str != NULL);
    str->push_back(w);
}

}  // internal



inline ::std::wstring ToWstring(const Utf8String& s) {
    ::std::wstring result;
    result.reserve(s.size());

    size_t i = 0;
    while ( i < s.size() ) {
        const Codepoint codepoint = Utf8ByteSequenceDecode(s, &i);
        internal::PushBackCodepoint(codepoint, &result);
    }

    return result;
}

inline ::std::wstring ToWstring(const Utf16String& s) {
}



template<class TToString, class TFromString>
inline TToString ConvertString(const TFromString&);

inline ::std::wstring ConvertString< ::std::wstring, Utf8String >(
    const Utf8String& utf8_str) {

}

inline ::std::wstring Utf8ToWstring(const ::std::string& utf8_str) {

}

inline Utf16String Utf8ToUtf16(const ::std::string& utf8_str) {
    Utf16String result;
    result.reserve(utf8_str.size());

    for ( size_t i = 0 ; i < utf8_str.size() ; ) {
        const uint32_t rune = Utf8ByteSequenceDecode(utf8_str, &i);
        internal::PushBackCodepoint<sizeof(Utf16Char)>(rune, &result);
    }

    return result;
}

inline Utf32String Utf8ToUtf32(const ::std::string& utf8_str) {
    Utf32String result;
    result.reserve(utf8_str.size());

    for ( size_t i = 0 ; i < utf8_str.size() ; ) {
        const uint32_t rune = Utf8ByteSequenceDecode(utf8_str, &i);
        internal::PushBackCodepoint<sizeof(Utf32Char)>(rune, &result);
    }

    return result;
}

}  // libv

#endif  // LIBV_UNICODE_H_
